/******************************************************************************* * Copyright (c) 2004, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.ui.tests.dynamicplugins; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import org.eclipse.core.runtime.IExtensionDelta; import org.eclipse.core.runtime.IRegistryChangeEvent; import org.eclipse.core.runtime.IRegistryChangeListener; import org.eclipse.core.runtime.Platform; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.tests.harness.util.UITestCase; import org.eclipse.ui.tests.leaks.LeakTests; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; /** * Baseclass for all dynamic tests. * * @since 3.1 */ public abstract class DynamicTestCase extends UITestCase implements IRegistryChangeListener { private volatile boolean addedRecieved; private Bundle newBundle; private volatile boolean removedRecieved; private WeakReference addedDelta; private WeakReference removedDelta; private ReferenceQueue queue; /** * @param testName */ public DynamicTestCase(String testName) { super(testName); } @Override protected void doTearDown() throws Exception { super.doTearDown(); try { removeBundle(); } finally { Platform.getExtensionRegistry().removeRegistryChangeListener(this); queue = null; } } /** * Get the bundle for this test. * * @return the bundle for this test */ protected final Bundle getBundle() { if (newBundle == null) { Platform.getExtensionRegistry().addRegistryChangeListener(this); reset(); queue = new ReferenceQueue(); // Just try to find the new perspective. Don't actually try to // do anything with it as the class it refers to does not exist. try { newBundle = DynamicUtils.installPlugin(getInstallLocation()); } catch (Exception e) { fail(e.getMessage()); } long startTime = System.currentTimeMillis(); long potentialEndTime = startTime + 5000; boolean timeToFail = false; while (!hasAddedEventPropagated() && !timeToFail) { processEvents(); timeToFail = System.currentTimeMillis() > potentialEndTime; Thread.yield(); } assertTrue("Expected ADDED event did not arrive in time", hasAddedEventPropagated()); try { LeakTests.checkRef(queue, addedDelta); } catch (IllegalArgumentException e1) { e1.printStackTrace(); } catch (InterruptedException e1) { e1.printStackTrace(); } processEvents(); Platform.getExtensionRegistry().removeRegistryChangeListener(this); } return newBundle; } /** * Return the namespace of the plugin that defines the extension point being * tested. Default is "org.eclipse.ui". * * @return the namespace of the declaring plugin */ protected String getDeclaringNamespace() { return WorkbenchPlugin.PI_WORKBENCH; } /** * Return the id of the extension to be tested. * * @return the id of the extension to be tested */ protected abstract String getExtensionId(); /** * Return the name of the extension point that is being tested. * * @return the extension point being tested */ protected abstract String getExtensionPoint(); /** * Return the install location of the bundle to test. * * @return the install location of the bundle to test */ protected abstract String getInstallLocation(); /** * Return a <code>Class</code> that we know to be in teh bundle to test. * * @return a <code>Class</code> that we know to be in teh bundle to test. May be <code>null</code>. * @since 3.1 */ protected String getMarkerClass() { return null; } /** * Tests to ensure that the marker class is released when the bundle is unloaded. * If <code>getMarkerClass()</code> returns <code>null</code> then this method * will always succeed. * * @throws Exception * @since 3.1 */ public void testClass() throws Exception { String className = getMarkerClass(); if (className == null) { return; } setName("testClass() for " + getClass().getName()); Bundle bundle = getBundle(); Class clazz = bundle.loadClass(className); assertNotNull(clazz); ReferenceQueue myQueue = new ReferenceQueue(); WeakReference ref = new WeakReference(clazz.getClassLoader(), myQueue); clazz = null; //null our refs bundle = null; removeBundle(); LeakTests.checkRef(myQueue, ref); } /** * Return whether the bundle ADDED event has been recieved. * * @return whether the bundle ADDED event has been recieved */ protected final boolean hasAddedEventPropagated() { return addedRecieved; } /** * Return whether the bundle REMOVED event has been recieved. * * @return whether the bundle REMOVED event has been recieved */ protected final boolean hasRemovedEventPropagated() { return removedRecieved; } /** * This method will ensure recording of addition and removal of extensions * described by a combination of * {@link DynamicTestCase#getDeclaringNamespace()}, * {@link DynamicTestCase#getExtensionPoint()}, and * {@link DynamicTestCase#getExtensionId()}. * * Custom implementationss should ensure that addition and removal of the * target extension are recorded. * * @see DynamicTestCase#setAddedEventPropagated(boolean) * @see DynamicTestCase#setRemovedEventPropagated(boolean) */ @Override public void registryChanged(IRegistryChangeEvent event) { IExtensionDelta delta = event.getExtensionDelta( getDeclaringNamespace(), getExtensionPoint(), getExtensionId()); if (delta != null) { if (delta.getKind() == IExtensionDelta.ADDED) { addedDelta = new WeakReference(delta, queue); setAddedEventPropagated(true); } else if (delta.getKind() == IExtensionDelta.REMOVED) { removedDelta = new WeakReference(delta, queue); setRemovedEventPropagated(true); } } } /** * Unload the bundle, if present. */ protected final void removeBundle() { if (newBundle != null) { Platform.getExtensionRegistry().addRegistryChangeListener(this); queue = new ReferenceQueue(); try { DynamicUtils.uninstallPlugin(newBundle); long startTime = System.currentTimeMillis(); long potentialEndTime = startTime + 5000; boolean timeToFail = false; while (!hasRemovedEventPropagated() && !timeToFail) { processEvents(); timeToFail = System.currentTimeMillis() > potentialEndTime; Thread.yield(); } assertTrue("Expected REMOVED event did not arrive in time", hasRemovedEventPropagated()); try { LeakTests.checkRef(queue, removedDelta); } catch (IllegalArgumentException e1) { e1.printStackTrace(); } catch (InterruptedException e1) { e1.printStackTrace(); } } catch (BundleException e) { fail(e.getMessage()); } finally { newBundle = null; } processEvents(); Platform.getExtensionRegistry().removeRegistryChangeListener(this); } } /** * Reset the added/removed flags. */ private void reset() { addedDelta = null; removedDelta = null; setAddedEventPropagated(false); setRemovedEventPropagated(false); } /** * Set whether the bundle ADDED event has been recieved. * * @param added * whether the bundle ADDED event has been recieved */ protected final void setAddedEventPropagated(boolean added) { this.addedRecieved = added; } /** * Set whether the bundle REMOVED event has been recieved. * * @param added * whether the bundle REMOVED event has been recieved */ protected final void setRemovedEventPropagated(boolean removed) { this.removedRecieved = removed; } }